﻿using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using System.IO;

public class MeshExporter : ScriptableObject
{
    private static int vertexOffset = 0;
    private static int normalOffset = 0;
    private static int uvOffset = 0;


    //User should probably be able to change this. It is currently left as an excercise for
    //the reader.
    private static string targetFolder
    {
        get
        {
            return $"{Application.dataPath}/Recast";
        }
    }

    private static bool CreateTargetFolder()
    {
        try
        {
            System.IO.Directory.CreateDirectory(targetFolder);
        }
        catch
        {
            EditorUtility.DisplayDialog("Error!", "Failed to create target folder!", "");
            return false;
        }

        return true;
    }

    [MenuItem("工具/导出分开的MESH对象")]
    static void ExportSelectionToSeparate()
    {
        if (!CreateTargetFolder())
            return;

        Transform[] selection = Selection.GetTransforms(SelectionMode.Editable | SelectionMode.ExcludePrefab);

        if (selection.Length == 0)
        {
            EditorUtility.DisplayDialog("No source object selected!", "Please select one or more target objects", "");
            return;
        }

        int exportedObjects = 0;
        var sceneName = EditorSceneManager.GetActiveScene().name;
        string filename = $"{targetFolder}/{sceneName}_seperated.bytes";
        int magic_group = ('S' << 24) | ('H' << 16) | ('M' << 8) | ('G' << 0);
        int magic = ('S' << 24) | ('H' << 16) | ('M' << 8) | ('D' << 0);
        int version = 1;
        MemoryStream ms_content = new MemoryStream();
        MemoryStream ms_dlen = new MemoryStream();
        using (FileStream fsWrite = new FileStream(filename, FileMode.Create))
        {
            BinaryWriter bcontent = new BinaryWriter(ms_content);

            BinaryWriter bdlen = new BinaryWriter(ms_dlen);
            bdlen.Write(magic_group);
            bdlen.Write(version);
            bdlen.Write(exportedObjects);

            for (int i = 0; i < selection.Length; i++)
            {
                var meshfilter = selection[i].GetComponentsInChildren(typeof(MeshFilter));
                for (int j = 0; j < meshfilter.Length; ++j)
                {
                    if (meshfilter[j] is MeshFilter mf)
                    {
                        var mesh = mf.sharedMesh;
                        if (null == mesh || null == mesh.vertices || mesh.vertices.Length <= 0)
                            continue;
                        ++exportedObjects;

                        int filePos = (int)bcontent.BaseStream.Length;

                        bcontent.Write(magic);
                        bcontent.Write(version);

                        int vcnt = mesh.vertices.Length;
                        int vpos = (int)bcontent.BaseStream.Position;
                        bcontent.Write(vcnt);

                        //bmin bmax
                        Vector3 bmin = Vector3.positiveInfinity;
                        Vector3 bmax = Vector3.negativeInfinity;

                        foreach (Vector3 v in mesh.vertices)
                        {
                            Vector3 wv = mf.transform.TransformPoint(v);
                            wv.x = -wv.x;
                            bcontent.Write(wv.x);
                            bcontent.Write(wv.y);
                            bcontent.Write(wv.z);
                            bmin.VMin(wv);
                            bmax.VMax(wv);
                        }

                        int tcnt = 0;
                        int tpos = (int)bcontent.BaseStream.Position;
                        bcontent.Write((System.Int16)tcnt);
                        for (int k = 0; k < mesh.subMeshCount; k++)
                        {
                            int[] triangles = mesh.GetTriangles(k);
                            if (triangles.Length % 3 == 0)
                            {
                                for (int t = 0; t < triangles.Length; t += 3)
                                {
                                    bcontent.Write(triangles[t]);
                                    bcontent.Write(triangles[t + 2]);
                                    bcontent.Write(triangles[t + 1]);
                                    tcnt += 3;
                                }
                            }
                        }
                        tcnt /= 3;

                        bcontent.Write(bmin.x);
                        bcontent.Write(bmin.y);
                        bcontent.Write(bmin.z);
                        bcontent.Write(bmax.x);
                        bcontent.Write(bmax.y);
                        bcontent.Write(bmax.z);

                        bcontent.Seek(vpos, SeekOrigin.Begin);
                        bcontent.Write(vcnt);
                        bcontent.Seek(tpos, SeekOrigin.Begin);
                        bcontent.Write((System.Int16)tcnt);
                        bcontent.Seek(0, SeekOrigin.End);
                        
                        int length = (int)(bcontent.BaseStream.Length - filePos);
                        if((System.Int16)tcnt != tcnt)
                        {
                            Debug.LogError($"tcnt={tcnt} Exeed Max Int16 {System.Int16.MaxValue}");
                        }
                        bdlen.Write(length);
                    }
                }
            }

            bdlen.Seek(8, SeekOrigin.Begin);
            bdlen.Write(exportedObjects);
            bdlen.Seek(0, SeekOrigin.End);
            bcontent.Seek(0, SeekOrigin.End);
            fsWrite.Write(ms_dlen.GetBuffer(), 0,(int)bdlen.BaseStream.Length);
            fsWrite.Write(ms_content.GetBuffer(), 0, (int)bcontent.BaseStream.Length);
            fsWrite.Flush();
            fsWrite.Close();
        }

        SwapEndian();
    }

    static void swap<T>(byte[] buff,ref int pos)
    {
        int n = 4;
        if(typeof(T) == typeof(int))
        {
            n = 4;
        }
        else if(typeof(T) == typeof(float))
        {
            n = 4;
        }
        else if(typeof(T) == typeof(short))
        {
            n = 2;
        }
        for(int i = 0; i < n/2;++i)
        {
            swap_byte(ref buff[pos + i], ref buff[pos + n - 1 - i]);
        }
        pos += n;
    }

    static void swap_byte(ref byte l,ref byte r)
    {
        byte t = l;
        l = r;
        r = t;
    }

    static void SwapEndian()
    {
        var sceneName = EditorSceneManager.GetActiveScene().name;
        string filename = $"{targetFolder}/{sceneName}_seperated.bytes";
        var bytes = System.IO.File.ReadAllBytes(filename);
        string filenameBigEndian = $"{targetFolder}/{sceneName}_seperated_bigendian.bin";

        MESH_GROUP mesh_group = new MESH_GROUP();
        int pos = 0;
        using (MemoryStream ms = new MemoryStream(bytes))
        {
            BinaryReader br = new BinaryReader(ms);
            mesh_group.magic = br.ReadInt32();
            swap<int>(bytes, ref pos);
            mesh_group.version = br.ReadInt32();
            swap<int>(bytes, ref pos);
            int mcnt = br.ReadInt32();
            swap<int>(bytes, ref pos);
            mesh_group.meshCnt = mcnt;
            mesh_group.heads = new HEAD_DATA[mcnt];
            for (int i = 0; i < mcnt; ++i)
            {
                mesh_group.heads[i] = new HEAD_DATA
                {
                    datalen = br.ReadInt32()
                };
                swap<int>(bytes, ref pos);
            }
            mesh_group.meshs = new MESH_DATA[mcnt];
            Vector3 tv = Vector3.zero;
            for (int i = 0; i < mcnt; ++i)
            {
                var mesh_data = new MESH_DATA();
                mesh_group.meshs[i] = mesh_data;
                mesh_data.magic = br.ReadInt32();
                swap<int>(bytes, ref pos);
                mesh_data.version = br.ReadInt32();
                swap<int>(bytes, ref pos);
                int vcnt = br.ReadInt32();
                swap<int>(bytes, ref pos);
                mesh_data.vertexs = new Vector3[vcnt];
                for (int j = 0; j < vcnt; ++j)
                {
                    mesh_data.vertexs[j] = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
                    swap<float>(bytes, ref pos);
                    swap<float>(bytes, ref pos);
                    swap<float>(bytes, ref pos);
                }
                int tcnt = br.ReadInt16() * 3;
                swap<short>(bytes, ref pos);
                mesh_data.triangles = new int[tcnt];
                var tris = mesh_group.meshs[i].triangles;
                for (int j = 0; j < tcnt; j += 3)
                {
                    tris[j] = br.ReadInt32();
                    swap<int>(bytes, ref pos);
                    tris[j + 1] = br.ReadInt32();
                    swap<int>(bytes, ref pos);
                    tris[j + 2] = br.ReadInt32();
                    swap<int>(bytes, ref pos);
                }
                mesh_group.meshs[i].bmin = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
                swap<float>(bytes, ref pos); swap<float>(bytes, ref pos); swap<float>(bytes, ref pos);
                mesh_group.meshs[i].bmax = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
                swap<float>(bytes, ref pos); swap<float>(bytes, ref pos); swap<float>(bytes, ref pos);
            }
            Debug.Assert(bytes.Length == pos);
        }
        System.IO.File.WriteAllBytes(filenameBigEndian, bytes);
    }
}

public static class MeshExporterHelper
{
    public static void VMin(ref this Vector3 v, Vector3 r)
    {
        v.x = Mathf.Min(v.x, r.x);
        v.y = Mathf.Min(v.y, r.y);
        v.z = Mathf.Min(v.z, r.z);
    }

    public static void VMax(ref this Vector3 v,Vector3 r)
    {
        v.x = Mathf.Max(v.x, r.x);
        v.y = Mathf.Max(v.y, r.y);
        v.z = Mathf.Max(v.z, r.z);
    }
}